package com.andova.glcamera

import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Message
import com.andova.glcamera.pool.HT_NAME_FRAME_HANDLER
import java.util.*

internal interface HandlerCall {
    fun guardedRun()
}

internal class HandlerImpl(looper: Looper) : Handler(looper) {
    private var mCall: HandlerCall? = null
    fun setCall(call: HandlerCall?) {
        mCall = call
    }

    override fun handleMessage(msg: Message) {
        mCall?.guardedRun()
    }
}

interface GlFrameHandler {
    fun strict(): Boolean
    fun handleFrame(cam0buf: FrameBuffer?, cam1buf: FrameBuffer?, syncBuffer: SyncFrameBuffer?, taskResult: Any?): Any?
}

class GlFrameHThread private constructor(name: String) : HandlerThread(name), HandlerCall {
    companion object {
        private var sInstance = GlFrameHThread(HT_NAME_FRAME_HANDLER)

        @Synchronized
        fun get(): GlFrameHThread {
            if (sInstance.state == State.TERMINATED) {
                sInstance = GlFrameHThread(HT_NAME_FRAME_HANDLER)
            }
            return sInstance
        }
    }

    private var mCount = 0
    private var mImpl: GlFrameHandler? = null
    private val mHandler: HandlerImpl by lazy { HandlerImpl(looper) }

    @Volatile
    private var mCam0Buffer = FrameBuffer.frame()

    @Volatile
    private var mCam1Buffer = FrameBuffer.frame()

    @Volatile
    private var syncBuffer = SyncFrameBuffer(FrameBuffer.frame(), FrameBuffer.frame())

    @Volatile
    private var lock = false

    private var coin: Boolean? = null
    private var taskResult: Any? = null
    private var handlerResult: Any? = null
    fun setHandler(handler: GlFrameHandler?) {
        mImpl = handler
    }

    override fun guardedRun() {
        mImpl ?: return
        if (mImpl!!.strict()) {
            if (syncBuffer.cam0Buffer.nv21?.isEmpty() != false || syncBuffer.cam1Buffer.nv21?.isEmpty() != false) {
                lock = false
                return
            }
        } else if (mCam0Buffer.nv21?.isEmpty() != false && mCam1Buffer.nv21?.isEmpty() != false) return
        val sync = syncBuffer.valid()
        handlerResult = mImpl!!.handleFrame(
                if (mCam0Buffer.nv21?.isNotEmpty() == true) mCam0Buffer else null,
                if (mCam1Buffer.nv21?.isNotEmpty() == true) mCam1Buffer else null,
                if (sync) syncBuffer else null,
                taskResult
        )
        mCam1Buffer.reset()
        mCam0Buffer.reset()
        if (sync) syncBuffer.reset()
        lock = false
    }

    @Synchronized
    fun tryStart() {
        ++mCount
        if (state != State.NEW) return
        start()
        mHandler.setCall(this)
    }

    @Synchronized
    fun tryQuit() {
        --mCount
        if (mCount > 0 || !isAlive) return
        mCount = 0
        mHandler.setCall(null)
        quit()
    }

    fun setCamera0Buffer(width: Int, height: Int, nv21: ByteArray, taskResult: Any?) {
        if (lock) return
        lock = true
        this.taskResult = taskResult
        mCam0Buffer.pack(width, height, nv21.clone())
        syncBuffer.pack(mCam0Buffer.clone(), null)
        coin?.apply {
            if (this) {
                mCam1Buffer.reset()
                syncBuffer.cam1Buffer.reset()
            } else coin = null
        } ?: apply { coin = true }
        if (isAlive) mHandler.sendEmptyMessage(0) else lock = false
    }

    fun setCamera1Buffer(width: Int, height: Int, nv21: ByteArray, taskResult: Any?) {
        if (lock) return
        lock = true
        this.taskResult = taskResult
        mCam1Buffer.pack(width, height, nv21.clone())
        syncBuffer.pack(null, mCam1Buffer.clone())
        coin?.apply {
            if (this) {
                coin = null
            } else {
                mCam0Buffer.reset()
                syncBuffer.cam0Buffer.reset()
            }
        } ?: apply { coin = false }
        if (isAlive) mHandler.sendEmptyMessage(1) else lock = false
    }

    fun obtainResult() = handlerResult
}

data class SyncFrameBuffer(var cam0Buffer: FrameBuffer, var cam1Buffer: FrameBuffer) {
    fun pack(cam0buf: FrameBuffer?, cam1buf: FrameBuffer?) {
        cam0buf?.apply { this@SyncFrameBuffer.cam0Buffer = this }
        cam1buf?.apply { this@SyncFrameBuffer.cam1Buffer = this }
    }

    fun reset() {
        cam0Buffer.reset()
        cam1Buffer.reset()
    }

    fun valid(): Boolean = !(cam0Buffer.nv21?.isEmpty() ?: true) && !(cam1Buffer.nv21?.isEmpty() ?: true)
}

data class FrameBuffer(var width: Int, var height: Int, var nv21: ByteArray?) {
    companion object {
        fun frame(): FrameBuffer = FrameBuffer(0, 0, null)
    }

    fun pack(width: Int, height: Int, nv21: ByteArray?) {
        this.width = width
        this.height = height
        this.nv21 = nv21
    }

    fun reset() {
        width = 0
        height = 0
        nv21?.apply { Arrays.fill(this, 0.toByte()) }
        nv21 = null
    }

    fun clone(): FrameBuffer = FrameBuffer(width, height, nv21?.clone())
    override fun equals(other: Any?): Boolean {
        other ?: return false
        if (this === other) return true
        if (javaClass != other.javaClass) return false

        other as FrameBuffer
        other.nv21 ?: return false

        if (width != other.width) return false
        if (height != other.height) return false
        if (nv21?.contentEquals(other.nv21!!) != true) return false

        return true
    }

    override fun hashCode(): Int {
        var result = width
        result = 31 * result + height
        result = 31 * result + (nv21?.contentHashCode() ?: 0)
        return result
    }
}

data class ResultBean(var id: String, var value: Any?) {
    fun clone(): ResultBean = ResultBean(id, value)
}